
#include <fcntl.h>
#include <sys/ioctl.h>
#include <pthread.h>

#include "iAP2_AppStub.h"
#include "StringUtils.h"
#include "CIpodLibWrapper.h"
#include "CarPlayInterface.h"
ulog_define( iAP2AppStub, kLogLevelTrace, kLogFlags_Default, "iAP2AppStub", NULL );
#define ias_ucat()					&log_category_from_name( iAP2AppStub )
#define ias_ulog( LEVEL, ... )		ulog( ias_ucat(), (LEVEL), __VA_ARGS__ )
#define ias_dlog( LEVEL, ... )		dlogc( ias_ucat(), (LEVEL), __VA_ARGS__ )

ulog_define( iAP2AppStubErr, kLogLevelTrace, kLogFlags_Default | kLogFlags_PrintError, "iAP2AppStubErr", NULL );
#define ias_ucat_err()					&log_category_from_name( iAP2AppStubErr )
#define ias_ulog_err( LEVEL, ... )		ulog( ias_ucat_err(), (LEVEL), __VA_ARGS__ )
#define ias_dlog_err( LEVEL, ... )		dlogc( ias_ucat_err(), (LEVEL), __VA_ARGS__ )

#define kUSBDetachProbeFile                 "/sys/bus/platform/devices/ci_hdrc.0/udc/ci_hdrc.0/state"
#define kUSBRoleStatusFile                  "/sys/kernel/debug/ci_hdrc.0/role"
#define kBluetoothAddressFilePath			"/data/bluetooth/deviceinfo.ini"

typedef struct
{
    char    deviceId[ 32 ];
    ipod_t  handle;
    int     transportType;
    int     started;

} iAP2Device;

typedef uint8_t     CarPlayConnectionType;

#define kCarPlayConnectionType_Unknown  0
#define kCarPlayConnectionType_Bonjour  1
#define kCarPlayConnectionType_iAP2     2

static void _handleRfcommData( int length, const uint8 * ptr );
static void setMediaAccessControlAddress();
static void TearDowniAP2( ipod_t handle );
static ipod_t GetPreferenceiAP2Handle();

static IPODObserver                 observer;
static WifiConfigurationInfo        gWifiConfigurationInfo;
static iAP2Device                   gUsbDevice;
static iAP2Device                   gBluetoothDevice;
static iAP2Device                   gWiFiDevice;

static iAP2InterfaceDelegate            delegate;

static CarPlay2iAP2InterfaceDelegate    gC2iDelegate;
static pthread_t                        giAP2UsbThread;
static pthread_t                        giAP2BluetoothThread;
static pthread_t                        giAP2WiFiThread;
static pthread_t                        gUsbDetachProbeThread;
static int                              gCarPlayStarted = 0;
static int                              gLocationSwitch = 0;
static CarPlayConnectionType            gCarPlayConnectionType = kCarPlayConnectionType_Unknown;
static char Mac_addr[6];
//===========================================================================================================================
//	Global
//===========================================================================================================================

int     giAP2TransportType = kiAP2TransportType_Unknown;
char    gDeviceId[ 18 ];
int     gWireConflictWireless = 0;

//===========================================================================================================================
//	_GetUsbRoleStatus
//===========================================================================================================================

OSStatus _GetUsbRoleStatus( char * outRoleStatus )
{
    OSStatus    err;
    FILE *      file;
    char        status[ 8 ] = { 0 };

    file = fopen( kUSBRoleStatusFile, "r" );
    if( file == NULL )
    {
        ias_ulog_err( kLogLevelNotice, "open %s failed.\n", kUSBRoleStatusFile );
        err = kNotHandledErr;
        goto exit;
    }

    if( fgets( status, 8, file ) == NULL )
    {
        ias_ulog_err( kLogLevelNotice, "fgets %s failed.\n", kUSBRoleStatusFile );
        err = kReadErr;
        goto exit;
    }

    err = kNoErr;
    memcpy( outRoleStatus, status, strlen( status ) );

exit:
    if( file )  fclose( file );
    return( err );
}

//===========================================================================================================================
//	RoleSwitch
//===========================================================================================================================

static int _RoleSwitchIsComplete()
{
    int fd = -1;
    int ret = -1;

    fd = open( "/dev/gadget_transfer", O_RDWR );
    if( fd < 0 )
    {
        ias_ulog_err( kLogLevelNotice, "open /dev/gadget_transfer failed.\n" );
        return ret;
    }

    ret = ioctl( fd, 0, NULL );
    if( ret )
    {
        ias_ulog( kLogLevelNotice, "role switch is complete.\n" );
        goto exit;
    }

exit: 
    close(fd);
    return ret;
}

static int _roleSwitch()
{
    int rc;
    int result = -1;
    int count = 0;
    struct libusb_device_handle  * deviceHandler = NULL;

    rc = libusb_init( NULL );
    if( rc < 0 )
    {
        ias_ulog_err( kLogLevelNotice, "Error initializing libusb: %s\n", libusb_error_name( rc ) );
        return -1;
    }

    deviceHandler = libusb_open_device_with_vid_pid( NULL, 0x05ac, 0x12a8 );
    if( !deviceHandler )
    {
        ias_ulog_err( kLogLevelNotice, "Error finding USB device.\n");
        goto exit;
    }

    // Detect if Apple device supports CarPlay in DeviceManager App, not in MiddleWare
    // _UsbVendorRequestGetSupportCapabilities();

    // _UsbVendorRequestHostModeSwitch();
    libusb_control_transfer( deviceHandler, 0x40, 0x51, 0x01, 0, NULL, 0, 0 );

    // role switch command.
    system( "echo usb_otg1_id=1 > /dev/hhhgpio" );

    while( !( result = _RoleSwitchIsComplete() ) && count++ < 400 )
    {
        usleep(5000);
    }

exit:
    if( deviceHandler ) libusb_close(deviceHandler);
    libusb_exit( NULL );

    deviceHandler = NULL;

    return result;
}

//===========================================================================================================================
//	_WireSetup
//===========================================================================================================================

static void * _UsbDetachProbeThread( void * inArg )
{
    bool started = true;
    FILE * file;
    char state[ 16 ];

    while( started )
    {
        file = fopen( kUSBDetachProbeFile, "r" );
        if( file == NULL )
        {
            ias_ulog_err( kLogLevelNotice, "open %s failed.\n", kUSBDetachProbeFile );
            break;
        }

        if( fgets( state, 16, file ) == NULL )
        {
            ias_ulog_err( kLogLevelNotice, "fgets %s failed.\n", kUSBDetachProbeFile );
        }

        if( !strcmp( state, "suspended\n" ) )
        {
            system( "echo usb_otg1_id=0 > /dev/hhhgpio" );
            started = false;

            if( giAP2TransportType == kiAP2TransportType_HostUsb && gWireConflictWireless )
            {
                if( gCarPlayStarted )
                {
                    ias_ulog_err( kLogLevelNotice, "wire setup on wireless then end AirPlay session when plug out.\n" );
                    gC2iDelegate.end_f();
                }
            }
            // Bad workaround. Sometimes AirPlaySession is blocked when iAP2 exits and usb plug-out, then simulate a message _AirPlayHandleSessionFinalized( AirPlay session ended )
            // Bad workaround. Optimized disconnect time.
            handleSessionFinalized();
            AirPlaySessionFinalized();

            ias_ulog_err( kLogLevelNotice, "Usb detached and role switch back.\n" );
            break;
        }
        fclose( file );
        file = NULL;

        usleep(100000);
    }

    if( file )  fclose( file );
    // return just for resolve complile error
    return inArg ;
}

static void * _iAP2UsbThread( void * inArg )
{
    int             ret;
    OSStatus        err = kNoErr;

    ret = _roleSwitch();

    if( ret != 1 )
    {
        ias_ulog_err( kLogLevelNotice, "role switch failed.\n");

        system( "echo usb_otg1_id=0 > /dev/hhhgpio" );
        require_noerr( err = kCommandErr, exit );
    }

    ret = pthread_create( &gUsbDetachProbeThread, NULL, _UsbDetachProbeThread, NULL );
    if( ret != 0 )
    {
        ias_ulog_err( kLogLevelNotice, "create UsbDetachProbeThread failed.\n");

        require_noerr( err = kNotHandledErr, exit );
    }
    pthread_detach( gUsbDetachProbeThread );

    ret = ipodLibInitFun( IPOD_FUN_MODE_CARPLAY, IPOD_CONN_TYPE_USB_HOST );
    if( ret < 0 )
    {
        ias_ulog_err( kLogLevelNotice, "open iAP2 device failed.\n");

        require_noerr( err = kOptionErr, exit );
    }

    setMediaAccessControlAddress();

    memcpy( gUsbDevice.deviceId, "kiAP2TransportType_HostUsb", strlen( "kiAP2TransportType_HostUsb" ) );
    gUsbDevice.transportType = kiAP2TransportType_HostUsb;

    ret = iap2Connect( &gUsbDevice.handle, gUsbDevice.deviceId );
    if( ret < 0 )
    {
        ias_ulog_err( kLogLevelNotice, "init iAP2 device failed.\n");
        TearDowniAP2( gUsbDevice.handle );

        require_noerr( err = kOptionErr, exit );
    }

exit:
    if( err )   ias_ulog_err( kLogLevelNotice, "init iAP2 over Usb Status: %d.\n", err );
    // return just for resolve complile error
    return inArg ;

}

static void _WireSetup()
{
    int ret;

    ret = pthread_create( &giAP2UsbThread, NULL, _iAP2UsbThread, NULL );
    if( ret != 0 )  ias_ulog_err( kLogLevelNotice, "create iAP2Thread over Usb failed.\n");

    pthread_detach( giAP2UsbThread );
}

//===========================================================================================================================
//	_getBluetoothAddress
//===========================================================================================================================

int _getBluetoothAddress( char * outAddress )
{
    FILE *  fd;
    char    buffer[ 64 ];
    char *  offset;
    char *  macAddress;

    fd = fopen( kBluetoothAddressFilePath, "r" );
    if( fd == NULL )
    {
        ias_ulog_err( kLogLevelNotice, "open file deviceinfo.ini failed.\n");
        return -1;
    }
    while( fgets( buffer, 63, fd ) != NULL )
    {
        offset = strstr( buffer, "bt_addr" );
        if( offset != NULL )
        {
            macAddress = strstr( offset, "=" );
            strcpy( outAddress, macAddress + 1 );
            outAddress[ 17 ] = '\0';
            fclose( fd );
            return 0;
        }
    }

	fclose( fd );
	return 0;
}

//===========================================================================================================================
//	set MediaAccessControlAddress
//===========================================================================================================================

static void setMediaAccessControlAddress()
{
    //uint8_t mac[6];
    //char bluetoothAddress[20] = {0};

   // _getBluetoothAddress( bluetoothAddress );
    //TextToHardwareAddress( bluetoothAddress, -1, 6, mac );
    notifyBtAddress(Mac_addr);
}

//===========================================================================================================================
//	_WirelessSetup
//===========================================================================================================================

static void * _iAP2Thread( void *inArg )
{
    OSStatus    err = kNoErr;
    int         ret;

    ret = ipodLibInitFun( IPOD_FUN_MODE_CARPLAY, IPOD_CONN_TYPE_BLUETOOTH );
    if( ret < 0 )
    {
        require_noerr( err = kOpenErr, exit );
    }

    wifiInformationSharing( gWifiConfigurationInfo );
    ipod_bt_spp_state_change( ipod_SppState_Connected );
    ipodRegistBtSppSendData( _handleRfcommData );
    setMediaAccessControlAddress();

    memcpy( gBluetoothDevice.deviceId, "kiAP2TransportType_Bluetooth", strlen( "kiAP2TransportType_Bluetooth" ) );
    gBluetoothDevice.transportType = kiAP2TransportType_Bluetooth;

    ret = iap2Connect( &gBluetoothDevice.handle, gBluetoothDevice.deviceId );
    if( ret < 0 )
    {
        TearDowniAP2( gBluetoothDevice.handle );
        require_noerr( err = kOptionErr, exit );
    }

exit:
    if( err )   ias_ulog_err( kLogLevelNotice, "init iAP2 over Bluetooth Status: %d.\n", err );
    // return just for resolve complile error
    return inArg ;

}

static void _WirelessSetup()
{
    int ret;

    ret = pthread_create( &giAP2BluetoothThread, NULL, _iAP2Thread, NULL );
    if( ret != 0 )  ias_ulog_err( kLogLevelNotice, "create iAP2Thread over bluetooth failed.\n");

    pthread_detach( giAP2BluetoothThread );
}

//===========================================================================================================================
//	_iAP2WiFiSetup
//===========================================================================================================================

// Device -> Accessory, read
void _sendWiFiData( const char *ptr, int length )
{
    ipod_t      handle;

    handle = gWiFiDevice.handle;
    if( handle == -1 )
    {
        ias_ulog_err( kLogLevelNotice, "sendWiFiData No Perferred iAP2 handle: %d.\n", handle );
        return;
    }

    sendWiFiData( handle, length, ptr );
}

static void * _iAP2WiFiThread( void *inArg )
{
    OSStatus    err = kNoErr;
    int         ret;

    ret = ipodLibInitFun( IPOD_FUN_MODE_CARPLAY, IPOD_CONN_TYPE_WiFi );
    if( ret < 0 )
    {
        require_noerr( err = kOpenErr, exit );
    }

    setWiFiDataCallback( gC2iDelegate.sendiAPMessage_f );
    setMediaAccessControlAddress();

    memcpy( gWiFiDevice.deviceId, "kiAP2TransportType_WiFi", strlen( "kiAP2TransportType_WiFi" ) );
    gWiFiDevice.transportType = kiAP2TransportType_WiFi;

    ret = iap2Connect( &gWiFiDevice.handle, gWiFiDevice.deviceId );
    if( ret < 0 )
    {
        TearDowniAP2( gWiFiDevice.handle );
        require_noerr( err = kOptionErr, exit );
    }

exit:
    if( err )   ias_ulog_err( kLogLevelNotice, "init iAP2 over Wi-Fi Status: %d.\n", err );
    // return just for resolve complile error
    return inArg ;

}

static void _iAP2WiFiSetup()
{
    int ret;

    ret = pthread_create( &giAP2WiFiThread, NULL, _iAP2WiFiThread, NULL );
    if( ret != 0 )  ias_ulog_err( kLogLevelNotice, "create iAP2Thread over WiFi failed.\n" );

    pthread_detach( giAP2WiFiThread );
}

//===========================================================================================================================
//	iAP2ReceiverServerDelegate
//===========================================================================================================================

static void _iAP2SessionState( ipod_t handle, bool status )
{
    ias_ulog( kLogLevelNotice, "iAP2SessionState: %d.\n", status );

    if( status == 0 || status == 2 )   TearDowniAP2( handle );
    else if( status == 1 )
    {
        if( gUsbDevice.handle == handle )
        {
            gUsbDevice.started = 1;
            ias_ulog_err( kLogLevelNotice, "iAP2 Over Usb Started.\n" );
        }
        else if( gBluetoothDevice.handle == handle )
        {
            gBluetoothDevice.started = 1;
            ias_ulog_err( kLogLevelNotice, "iAP2 Over Bluetooth Started.\n" );
        }
        else if( gWiFiDevice.handle == handle )
        {
            gWiFiDevice.started = 1;
            ias_ulog_err( kLogLevelNotice, "iAP2 Over WiFi Started.\n" );
        }
        else    ias_ulog_err( kLogLevelNotice, "iAP2SessionState iAP2 handle err.\n" );

        ias_ulog_err( kLogLevelNotice, "iAP2 is still running Usb: %d, Bluetooth: %d, WiFi: %d.\n", gUsbDevice.started, gBluetoothDevice.started, gWiFiDevice.started );
    }
}

static ipod_t GetPreferenceiAP2Handle()
{
    if( gUsbDevice.started == 1 )               return gUsbDevice.handle;
    else if( gWiFiDevice.started == 1 )         return gWiFiDevice.handle;
    else if( gBluetoothDevice.started == 1 )    return gBluetoothDevice.handle;
    else
    {
        ias_ulog_err( kLogLevelNotice, "There is no perferred iAP2 handle.\n" );
        return -1;
    }
}

void    AirPlaySessionControlDisableBluetooth()
{
    if( gBluetoothDevice.handle > 0 )   TearDowniAP2( gBluetoothDevice.handle );
}

static void _DeviceInformationUpdate( ipod_t handle,  const char * name )
{
    ias_ulog( kLogLevelNotice, "DeviceInformationUpdate: %s.\n", name );

    handleDeviceInformationUpdate( name );
}

//===========================================================================================================================
//	PlaybackAttributes
//===========================================================================================================================

static void _PlaybackQueueIndex( ipod_t handle, uint32 index  )
{
    char    MediaItemTitle[256];
    char    MediaItemAlbumTitle[256];
    char    MediaItemArtist[256];
    int     MediaDuration = 0;

    memset( MediaItemTitle, 0, 256 );
    memset( MediaItemAlbumTitle, 0, 256 );
    memset( MediaItemArtist, 0, 256 );

    getPlayingTrackTitle( handle, MediaItemTitle );
    getPlayingTrackAlbum( handle, MediaItemAlbumTitle );
    getPlayingTrackArtist( handle, MediaItemArtist );
    getPlayingTrackDuration( handle, &MediaDuration );

    ias_ulog( kLogLevelNotice, "MediaItem MediaItemTitle: %s, MediaItemAlbumTitle: %s, MediaItemArtist: %s, MediaDuration: %d.\n",
        MediaItemTitle, MediaItemAlbumTitle, MediaItemArtist, MediaDuration );

    //handleNowPlayingUpdateMediaItemAttributes( MediaItemTitle, MediaItemAlbumTitle, MediaItemArtist, MediaDuration );
}

static void _PlaybackStatus( ipod_t handle, int status  )
{
    ias_ulog( kLogLevelNotice, "PlaybackStatus: %d.\n", status );

   // handleNowPlayingUpdatePlaybackAttributes( status, -1, -1, -1 );
}

static void _PlaybackShuffleMode( ipod_t handle, int shuffle )
{
    ias_ulog( kLogLevelNotice, "PlaybackShuffleMode: %d.\n", shuffle );
    //handleNowPlayingUpdatePlaybackAttributes( -1, shuffle, -1, -1 );
}

static void _PlaybackRepeatMode( ipod_t handle, int repeat )
{
    ias_ulog( kLogLevelNotice, "PlaybackRepeatMode: %d.\n", repeat );
    //handleNowPlayingUpdatePlaybackAttributes( -1, -1, repeat, -1 );
}

static void _PlaybackElapsedTimeInMilliseconds( ipod_t handle, uint32 elapsedTime )
{
    static uint8_t tick = 0;
    if( tick++ % 10 == 0 )   ias_ulog( kLogLevelNotice, "PlaybackElapsedTimeInMilliseconds: %d.\n", elapsedTime );

    //handleNowPlayingUpdatePlaybackAttributes( -1, -1, -1, elapsedTime );
}

//===========================================================================================================================
//	MediaLibraryUpdate
//===========================================================================================================================

static void _MediaLibraryUpdateProgress( ipod_t handle, uint8 progress )
{
    if( progress == 0 || progress == 10 || progress == 20 || progress == 30 || progress == 40 || progress == 50 || progress == 100 )
        ias_ulog( kLogLevelNotice, "MediaLibraryUpdateProgress: %d.\n", progress );

    //handleMediaLibraryUpdateProgress( progress );
}

static void _ResetMusicList()
{
    uint32      number;
    ipod_t      handle;

    handle = GetPreferenceiAP2Handle();
    if( handle == -1 )
    {
        ias_ulog_err( kLogLevelNotice, "ResetMusicList No Perferred iAP2 handle: %d.\n", handle );
        return;
    }

    resetMusicList( handle, &number );
    ias_ulog( kLogLevelNotice, "ResetMusicList: %d.\n", number );
}

static void _SelectMusicList( int index )
{
    uint32      number = 0;
    ipod_t      handle;

    handle = GetPreferenceiAP2Handle();
    if( handle == -1 )
    {
        ias_ulog_err( kLogLevelNotice, "SelectMusicList No Perferred iAP2 handle: %d.\n", handle );
        return;
    }

    selectMusicList( handle, index, -1, &number );
    ias_ulog( kLogLevelNotice, "SelectMusicList: %d.\n", number );
}

static void _BackMusicList()
{
    uint32      number = 0;
    ipod_t      handle;

    handle = GetPreferenceiAP2Handle();
    if( handle == -1 )
    {
        ias_ulog_err( kLogLevelNotice, "BackMusicList No Perferred iAP2 handle: %d.\n", handle );
        return;
    }

    selectMusicList( handle, -1, -1, &number );
    ias_ulog( kLogLevelNotice, "BackMusicList: %d.\n", number );
}

static void _GetMusicList( int index, int count )
{
    ias_ulog( kLogLevelNotice, "GetMusicList index: %d, count: %d.\n", index, count );

    ipod_t              handle;
    int                 ret;
    C_IPODMusicList     list;
    C_IPODMusicSource   source;
    char            senddata[ 3200 ] = {0};
    int             offset = 0;
    int             endIndex = 0;
    int             sourceNumber = 0;

    handle = GetPreferenceiAP2Handle();
    if( handle == -1 )
    {
        ias_ulog_err( kLogLevelNotice, "GetMusicList No Perferred iAP2 handle: %d.\n", handle );
        return;
    }

    ret = getMusicList_c( handle, index, count, &list );

    if( ret >= 0 )
    {
        // totalnum - 当前层级下记录总数
        senddata[ offset++ ] = ( ( list.totalnum >> 8 ) & 0xFF );
        senddata[ offset++ ] = (   list.totalnum        & 0xFF );
    
        // categorytype - 当前层级的类型 {@see IPODCategoryType}
        senddata[ offset++ ] = list.categorytype;

        // start - 开始序号
        senddata[ offset++ ] = ( ( list.start >> 8 ) & 0xFF );
        senddata[ offset++ ] = (   list.start        & 0xFF);

        // end - 结束序号
        endIndex = offset;
        senddata[ offset++ ] = ( ( list.end >> 8 ) & 0xFF );
        senddata[ offset++ ] = (   list.end        & 0xFF );

        ias_ulog( kLogLevelNotice, "List totalnum: %d, categorytype: %d, start: %d, end: %d.\n", list.totalnum, list.categorytype, list.start, list.end );

        for( int i = 0; i < list.size; i++ )
        {
            memcpy( &source, &list.elements[ i ], sizeof( C_IPODMusicSource ) );
            sourceNumber++;

            // name
            int nameLength = strlen( source.name );

            if( offset + nameLength + 4 <= 3200 )
            {
                // index
                senddata[ offset++ ] = ( ( source.index >> 8 ) & 0xFF );
                senddata[ offset++ ] = (   source.index        & 0xFF );

                // name
                senddata[ offset++ ] = ( ( nameLength >> 8 ) & 0xFF );
                senddata[ offset++ ] = (   nameLength        & 0xFF );
                memcpy( &senddata[offset], source.name, nameLength );

                offset += nameLength;
                ias_ulog( kLogLevelNotice, "List Source index: %d, name: %s.\n", source.index, source.name );
            }
            else
            {
                ias_ulog( kLogLevelNotice, "List request once is too large offset: %d.\n", offset );
                // end - 结束序号
                senddata[ endIndex++ ] = ( ( ( list.start + sourceNumber ) >> 8 ) & 0xFF );
                senddata[ endIndex++ ] = (   ( list.start + sourceNumber )        & 0xFF );
                break;
            }
            memset( source.name, '\0', 128 );
            source.index = 0;
        }

        //handlePlayListInfo( senddata, offset );
    }
}

static void _GetMusicPlayingList( int index, int count )
{
    ias_ulog( kLogLevelNotice, "GetMusicPlayingList index: %d, count: %d.\n", index, count );

    ipod_t              handle;
    int                 ret;
    C_IPODMusicList     list;
    C_IPODMusicSource   source;
    char            senddata[ 3200 ] = {0};
    int             offset = 0;
    int             endIndex = 0;
    int             sourceNumber = 0;

    handle = GetPreferenceiAP2Handle();
    if( handle == -1 )
    {
        ias_ulog_err( kLogLevelNotice, "GetMusicPlayingList No Perferred iAP2 handle: %d.\n", handle );
        return;
    }

    ret = getMusicPlayingList( handle, index, count, &list );

    if( ret >= 0 )
    {
        // totalnum - 当前层级下记录总数
        senddata[ offset++ ] = ( ( list.totalnum >> 8 ) & 0xFF );
        senddata[ offset++ ] = (   list.totalnum        & 0xFF );

        // categorytype - 当前层级的类型 {@see IPODCategoryType}
        senddata[ offset++ ] = list.categorytype;

        // start - 开始序号
        senddata[ offset++ ] = ( ( list.start >> 8 ) & 0xFF );
        senddata[ offset++ ] = (   list.start        & 0xFF);

        // end - 结束序号
        endIndex = offset;
        senddata[ offset++ ] = ( ( list.end >> 8 ) & 0xFF );
        senddata[ offset++ ] = (   list.end        & 0xFF );

        ias_ulog( kLogLevelNotice, "PlayList totalnum: %d, categorytype: %d, start: %d, end: %d.\n", list.totalnum, list.categorytype, list.start, list.end );

        for( int i = 0; i < list.size; i++ )
        {
            memcpy( &source, &list.elements[ i ], sizeof( C_IPODMusicSource ) );
            sourceNumber++;

            // name
            int nameLength = strlen( source.name );

            if( offset + nameLength + 4 <= 3200 )
            {
                // index
                senddata[ offset++ ] = ( ( source.index >> 8 ) & 0xFF );
                senddata[ offset++ ] = (   source.index        & 0xFF );

                // name
                senddata[ offset++ ] = ( ( nameLength >> 8 ) & 0xFF );
                senddata[ offset++ ] = (   nameLength        & 0xFF );
                memcpy( &senddata[offset], source.name, nameLength );

                offset += nameLength;
                ias_ulog( kLogLevelNotice, "PlayList Source index: %d, name: %s.\n", source.index, source.name );
            }
            else
            {
                ias_ulog( kLogLevelNotice, "PlayList request once is too large offset: %d.\n", offset );
                // end - 结束序号
                senddata[ endIndex++ ] = ( ( ( list.start + sourceNumber ) >> 8 ) & 0xFF );
                senddata[ endIndex++ ] = (   ( list.start + sourceNumber )        & 0xFF );
                break;
            }
            memset( source.name, '\0', 128 );
            source.index = 0;
        }

       // handlePlayListInfo( senddata, offset );
    }
}

static void _SelectMusicPlayingList( int index )
{
    ipod_t      handle;

    handle = GetPreferenceiAP2Handle();
    if( handle == -1 )
    {
        ias_ulog_err( kLogLevelNotice, "SelectMusicPlayingList No Perferred iAP2 handle: %d.\n", handle );
        return;
    }

    selectMusicPlayingList( handle, index );
    ias_ulog( kLogLevelNotice, "SelectMusicPlayingList: %d.\n", index );
}

static void _MediaPlayingListUpdate( ipod_t handle, int index )
{
    ias_ulog( kLogLevelNotice, "MediaPlayingListUpdate index: %d.\n", index );

    //handleMediaPlayListUpdate( index );
}

static void _PlaybackQueueListAvailable( ipod_t handle, bool available )
{
    ias_ulog( kLogLevelNotice, "PlaybackQueueListAvailable available: %d.\n", available );

   // handlePlaybackQueueListAvailable( available );
}

//===========================================================================================================================
//	NowPlayingUpdate - MediaItem
//===========================================================================================================================

static void _MediaItemArtworkFileTransferIdentifier( ipod_t handle, const char * path )
{
    ias_ulog( kLogLevelNotice, "MediaItemArtworkFileTransferIdentifier: %s.\n", path );

    //handleMediaItemArtworkFileTransferIdentifier( path );
}

static void _CallStateUpdate( ipod_t handle, CallStateInformation * callState )
{
    ias_ulog( kLogLevelNotice, "CallStateUpdate RemoteID: %s, DisplayName: %s, Status: %d, Direction: %d.\n", 
        callState->call_state_phone_number, callState->call_state_network_sent_caller_name, callState->call_state_status, callState->call_state_direction );

    //handleCallStateUpdate( callState->call_state_phone_number, callState->call_state_network_sent_caller_name,
    //    callState->call_state_status, callState->call_state_direction );
}

static void _LocationSwitch( ipod_t handle, int status, CIAP2LocationType * type )
{
    ias_ulog( kLogLevelNotice, "LocationSwitch: %d.\n", status );

    gLocationSwitch = status;
}

static void _IdentifierNotificationUpdated( ipod_t handle,  CDeviceTransportNotification * di )
{
    ias_ulog( kLogLevelNotice, "BluetoothTransportIdentifier: %s, USBTransportIdentifier: %s\n",
        di->BluetoothTransportIdentifier, di->USBTransportIdentifier );

    //handleIdentifierNotificationUpdated( di->BluetoothTransportIdentifier, di->USBTransportIdentifier );

    memset( gDeviceId, 0, 18 );
    memcpy( gDeviceId, di->BluetoothTransportIdentifier, strlen( di->BluetoothTransportIdentifier ) );

    if( gC2iDelegate.controllerEqual_f() == 0 )
    {
        ias_ulog_err( kLogLevelNotice, "Global CarPlayControllerRef doesn't equal current iAP2 deviceID, so not upload.\n" );
        return;
    }
    BonjourCarPlayAvailability();
}

//===========================================================================================================================
//	RouteGuidance
//===========================================================================================================================

void SecondsToHM( int64_t inSeconds, int * outHours, int * outMinutes )
{
    int seconds;
    int hours, minutes;

    hours   = inSeconds / kSecondsPerHour;
    seconds = inSeconds % kSecondsPerHour;
    minutes = seconds   / kSecondsPerMinute;
    seconds = seconds   % kSecondsPerMinute;

    if( outHours )      *outHours   = hours;
    if( outMinutes )    *outMinutes = minutes;
}

static void _RouteGuidanceUpdate( ipod_t handle, RouteGuidanceUpdate * rgu )
{
    int     hours, minutes;
    int64_t seconds = rgu->timeRemainingToDestination;

    SecondsToHM( seconds, &hours, &minutes );

    // ias_ulog( kLogLevelNotice, "\n - RouteGuidanceState: %d, \n - ManeuverState: %d, \n - CurrentRoadName: %s, "
    //     "\n - DestinationName: %s, \n - DistanceRemainingDisplayStr: %s, \n - DistanceRemainingDisplayUnits: %d, "
    //     "\n - DistanceToNextManeuverDisplayStr: %s, \n - DistanceToNextManeuverDisplayUnits: %d, \n - TimeRemainingToDestination hours: %d, minutes: %d.\n", 
    //     rgu->routeGuidanceState, rgu->maneuverState, rgu->currentRoadName, rgu->destinationName, 
    //     rgu->distanceRemainingDisplayStr, rgu->distanceRemainingDisplayUnits,
    //     rgu->distanceToNextManeuverDisplayStr, rgu->distanceToNextManeuverDisplayUnits,
    //     hours, minutes );

   // handleRouteGuidanceUpdate( rgu->routeGuidanceState, rgu->maneuverState, rgu->currentRoadName, rgu->destinationName, rgu->distanceRemainingDisplayStr, rgu->distanceRemainingDisplayUnits,
  //      rgu->distanceToNextManeuverDisplayStr, rgu->distanceToNextManeuverDisplayUnits, hours, minutes );
}

static void _RouteGuidanceManeuverInformation( ipod_t handle, RouteGuidanceManeuverUpdate * rgmu )
{
    ias_ulog( kLogLevelNotice, "maneuverType: %d, afterManeuverRoadName: %s, drivingSide: %d.\n", rgmu->maneuverType, rgmu->afterManeuverRoadName, rgmu->drivingSide );

    //handleRouteGuidanceManeuverInformation( rgmu->maneuverType, rgmu->afterManeuverRoadName, rgmu->drivingSide );
}

//===========================================================================================================================
//	Out-of-Band Bluetooth Pairing
//===========================================================================================================================

static void _OOBBTPairingLinkKeyInformation( ipod_t handle, OOBLinkKeyInformation * linkKey )
{
    // ias_ulog( kLogLevelNotice, "linkKey: %02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X\n",
    //     linkKey->link_key[0], linkKey->link_key[1], linkKey->link_key[ 2], linkKey->link_key[ 3], linkKey->link_key[ 4], linkKey->link_key[ 5], linkKey->link_key[ 6], linkKey->link_key[ 7],
    //     linkKey->link_key[8], linkKey->link_key[9], linkKey->link_key[10], linkKey->link_key[11], linkKey->link_key[12], linkKey->link_key[13], linkKey->link_key[14], linkKey->link_key[15] );
    //handleOobBtPairingLinkKeyInformation( linkKey->link_key, linkKey->link_key_len, linkKey->mac_address );
}

//===========================================================================================================================
//	RFCOMM data from accessory to device
//===========================================================================================================================

static void _handleRfcommData( int length, const uint8 * ptr )
{
    handleRfcommData( ptr, length );
}

static void _WirelessCarPlayUpdate( ipod_t handle,  int available )
{
    if( giAP2TransportType == kiAP2TransportType_HostUsb )
    {
        ias_ulog( kLogLevelNotice, "WireCarPlay ignore WirelessCarPlayUpdate.\n" );
        return;
    }
    ias_ulog( kLogLevelNotice, "WirelessCarPlayUpdate Available: %d\n", available );
    if( available == 0 )    handleCarPlayAvailable( 0 );
}

void BonjourCarPlayAvailability()
{
    if( gCarPlayStarted == 1 )
    {
        ias_ulog( kLogLevelNotice, "BonjourCarPlayAvailability CarPlay has already been started.\n" );
        return;
    }
    if( giAP2TransportType == kiAP2TransportType_HostUsb )
    {
        ias_ulog_err( kLogLevelNotice, "BonjourCarPlayAvailability over Usb, so not upload and connect directly.\n" );
        gC2iDelegate.carPlayStartBonjour_f();
        return;
    }
    ias_ulog_err( kLogLevelNotice, "Global CarPlayControllerRef equal current iAP2 deviceID, so upload.\n" );

    gCarPlayConnectionType = kCarPlayConnectionType_Bonjour;
    handleCarPlayAvailable( 1 );
}

void _CarPlayAvailability( ipod_t handle, CarPlayAvailInfo * info )
{
    ias_ulog( kLogLevelNotice, "CarPlayAvailability WireAvailable: %d, WirelessAvailable: %d.\n", info->wireAvailable, info->wirelessAvailable );

    if( gCarPlayStarted == 1 )
    {
        ias_ulog( kLogLevelNotice, "CarPlayAvailability CarPlay has already been started.\n" );
        return;
    }

    if( giAP2TransportType == kiAP2TransportType_HostUsb )
    {
        ias_ulog_err( kLogLevelNotice, "CarPlayAvailability over Usb, so not upload and connect directly.\n" );
        if( info->wireAvailable == 1 )  iap2CarPlayStartSession( handle, 0 );
    }

    gCarPlayConnectionType = kCarPlayConnectionType_iAP2;
    if( giAP2TransportType == kiAP2TransportType_Bluetooth )
    {
        //handleCarPlayAvailable( info->wirelessAvailable );
    }
}

//===========================================================================================================================
//	_setup
//===========================================================================================================================

static void _setup(
    int             type,
    uint16_t        vendorId,
    uint16_t        productId,
    const char      *address,
    int             same )
{
    ias_ulog_err( kLogLevelNotice, "iAP2 setup type: %d, same device: %d.\n", type, same );

    if( !gCarPlayStarted )
    {
        if( gBluetoothDevice.handle > 0 ) TearDowniAP2( gBluetoothDevice.handle );
    }

    if( gCarPlayStarted && type == kiAP2TransportType_HostUsb )
    {
        ias_ulog_err( kLogLevelNotice, "CarPlay has already started and not support switch to CarPlay over USB until now.\n" );
        return;
    }

    observer.on_connect_status_changed              = _iAP2SessionState;
    observer.on_device_information_changed          = _DeviceInformationUpdate;
    observer.on_device_transportid_changed          = _IdentifierNotificationUpdated;
    observer.on_shuffle_mode_changed                = _PlaybackShuffleMode;
    observer.on_repeat_mode_changed                 = _PlaybackRepeatMode;
    observer.on_track_position_changed              = _PlaybackElapsedTimeInMilliseconds;
    observer.on_playback_state_changed              = _PlaybackStatus;
    observer.on_track_changed                       = _PlaybackQueueIndex;
    observer.on_album_artwark_changed               = _MediaItemArtworkFileTransferIdentifier;
    observer.on_media_update_progress               = _MediaLibraryUpdateProgress;
    observer.on_call_state_changed                  = _CallStateUpdate;
    observer.on_location_enable_changed             = _LocationSwitch;
    observer.on_wireless_carplay_update             = _WirelessCarPlayUpdate;
    observer.on_route_guidance_changed              = _RouteGuidanceUpdate;
    observer.on_route_guidance_maneuver_changed     = _RouteGuidanceManeuverInformation;
    observer.on_device_link_key                     = _OOBBTPairingLinkKeyInformation;
    observer.on_carplay_availability                = _CarPlayAvailability;
    observer.on_media_playing_list_changed          = _MediaPlayingListUpdate;
    observer.on_playback_queuelist_available        = _PlaybackQueueListAvailable;

    registerObserver( &observer );

    giAP2TransportType = type;

    if ( type == kiAP2TransportType_HostUsb )           _WireSetup();
    else if( type == kiAP2TransportType_Bluetooth )     _WirelessSetup();
    else if( type == kiAP2TransportType_WiFi )          _iAP2WiFiSetup();
}

void iAP2OverCarPlaySetup()
{
    _setup( kiAP2TransportType_WiFi, 0, 0, NULL, 0 );
}

//===========================================================================================================================
//	TearDowniAP2
//===========================================================================================================================

static void TearDowniAP2( ipod_t handle )
{
    if( gUsbDevice.handle == handle )
    {
        gUsbDevice.started = 0;
        gUsbDevice.handle = -1;

        iap2Disconnect( &handle );
        ias_ulog_err( kLogLevelNotice, "finalize iAP2 Over Usb.\n");
    }
    else if( gBluetoothDevice.handle == handle )
    {
        gBluetoothDevice.started = 0;
        gBluetoothDevice.handle = -1;

        iap2Disconnect( &handle );
        ias_ulog_err( kLogLevelNotice, "finalize iAP2 Over Bluetooth.\n");
    }
    else if( gWiFiDevice.handle == handle )
    {
        gWiFiDevice.started = 0;
        gWiFiDevice.handle = -1;

        iap2Disconnect( &handle );
        ias_ulog_err( kLogLevelNotice, "finalize iAP2 Over WiFi.\n");
    }
    else    ias_ulog_err( kLogLevelNotice, "finalize iAP2 handle err.\n");

    ias_ulog_err( kLogLevelNotice, "iAP2 is still running Usb: %d, Bluetooth: %d, WiFi: %d.\n", gUsbDevice.started, gBluetoothDevice.started, gWiFiDevice.started );
}

//===========================================================================================================================
//	build and send the Custom NMEA Sentences (PASCD)
//===========================================================================================================================

static void _sendPascdSentence( char transmissionState, int sampleCount, double * speed )
{
    char        PASCDNmeaSentences[128];
    char        NmeaSentences[128];
    float       timeOffset_i;
    double      speed_i;
    char        Checksum = 0;
    ipod_t      handle;

    if( !gLocationSwitch )  return;

    handle = GetPreferenceiAP2Handle();
    if( handle == -1 )
    {
        ias_ulog_err( kLogLevelNotice, "sendPascdSentence No Perferred iAP2 handle: %d.\n", handle );
        return;
    }

    struct timeval  now;
    gettimeofday( &now, NULL );

    sprintf( PASCDNmeaSentences, "PASCD,%ld.%.3ld,C,%c,0,%d", now.tv_sec, now.tv_usec / 1000, transmissionState, sampleCount );

    for( int i = 0; i < sampleCount; i++ )
    {
        timeOffset_i = (float)i / (float)sampleCount;
        // convert km/h to m/s
        speed_i = speed[i] / 3.6;
        sprintf( PASCDNmeaSentences, "%s,%.2f,%.3f", PASCDNmeaSentences, timeOffset_i, speed_i );
    }

    for( int i = 0; i < strlen( PASCDNmeaSentences ); i++ )
    {
        Checksum = Checksum ^ PASCDNmeaSentences[i];
    }

    sprintf( NmeaSentences, "$%s*%X", PASCDNmeaSentences, Checksum );
    
    setLocationInformation( handle, NmeaSentences );
}

//===========================================================================================================================
//	send the NMEA Sentences
//===========================================================================================================================

static void _sendNmeaSentence( const char * nmeaSentence )
{
    ipod_t      handle;

    if( gLocationSwitch )
    {
        handle = GetPreferenceiAP2Handle();
        if( handle == -1 )
        {
            ias_ulog_err( kLogLevelNotice, "sendNmeaSentence No Perferred iAP2 handle: %d.\n", handle );
            return;
        }
        setLocationInformation( handle, nmeaSentence );
    }
}

//===========================================================================================================================
//	RFCOMM data from device to accessory
//===========================================================================================================================

static void _sendRfcommData( char *ptr, int length )
{
    // ias_ulog_err( kLogLevelNotice, "RfcommData ( Device -> Accessory ) length: %d.\n", length );

    ipod_t      handle;

    handle = gBluetoothDevice.handle;
    if( handle == -1 )
    {
        ias_ulog_err( kLogLevelNotice, "sendRfcommData No Perferred iAP2 handle: %d.\n", handle );
        return;
    }

    ipod_BT_spp_data_pro( handle, length, ptr );
}

//===========================================================================================================================
//	_sendWiFiInformation
//===========================================================================================================================

static void _sendWiFiInformation(
    const char *    ssid,
    const char *    passphrase,
    int             securityType,
    const char *    channel )
{
    ias_ulog( kLogLevelNotice, "sendWiFiInformation SSID: %s, Passphrase: %s\n", ssid, passphrase );
    
    memset( gWifiConfigurationInfo.wifi_ssid, 0, 128 );
    memset( gWifiConfigurationInfo.pass_phrase, 0, 128 );

    memcpy( gWifiConfigurationInfo.wifi_ssid, ssid, strlen( ssid ) );
    memcpy( gWifiConfigurationInfo.pass_phrase, passphrase, strlen( passphrase ) );

    gWifiConfigurationInfo.security_type = 2;
    gWifiConfigurationInfo.channel = 149;
}

//===========================================================================================================================
//	_SetMacAddr_
//===========================================================================================================================
static void _SetMacAddr(char * addr)
{
	memcpy(Mac_addr,addr,6);
}
//===========================================================================================================================
//	Out-of-Band Bluetooth Pairing
//===========================================================================================================================

static void _sendOobBtPairingCompletionInformation( int resultCode )
{
    ias_ulog( kLogLevelNotice, "sendOobBtPairingCompletionInformation resultCode: %d\n", resultCode );

    ipod_t      handle;

    handle = GetPreferenceiAP2Handle();
    if( handle == -1 )
    {
        ias_ulog_err( kLogLevelNotice, "sendOobBtPairingCompletionInformation No Perferred iAP2 handle: %d.\n", handle );
        return;
    }

    iap2OOBBTPairingCompletionInformation( handle, resultCode );
}

//===========================================================================================================================
//	_sendCarPlayStartSession
//===========================================================================================================================

static void _sendCarPlayStartSession( int allow )
{
    ias_ulog_err( kLogLevelNotice, "sendCarPlayStartSession allow: %d\n", allow );

    ipod_t      handle;

    if( allow == 1 )
    {
        if( gCarPlayConnectionType == kCarPlayConnectionType_Bonjour )  gC2iDelegate.carPlayStartBonjour_f();
        if( gCarPlayConnectionType == kCarPlayConnectionType_iAP2 )
        {
            handle = GetPreferenceiAP2Handle();
            if( handle == -1 )
            {
                ias_ulog_err( kLogLevelNotice, "sendCarPlayStartSession No Perferred iAP2 handle: %d.\n", handle );
                return;
            }
            iap2CarPlayStartSession( handle, 1 );
        }
    }
    else
    {
        AirPlaySessionFinalized();
    }
}

//===========================================================================================================================
//	AirPlaySessionCreated
//===========================================================================================================================

void    AirPlaySessionCreated()
{
    gCarPlayStarted = 1;
}

//===========================================================================================================================
//	AirPlaySessionFinalized
//===========================================================================================================================

void    AirPlaySessionFinalized()
{
    giAP2TransportType = kiAP2TransportType_Unknown;
    gCarPlayStarted = 0;
    gLocationSwitch	= 0;
    gWireConflictWireless = 0;
    gCarPlayConnectionType = kCarPlayConnectionType_Unknown;
    memset( gDeviceId, 0, 18 );

    if( gUsbDevice.handle > 0 )          TearDowniAP2( gUsbDevice.handle );
    if( gWiFiDevice.handle > 0 )         TearDowniAP2( gWiFiDevice.handle );
    if( gBluetoothDevice.handle > 0 )    TearDowniAP2( gBluetoothDevice.handle );
    handleCarPlayExit();
}

//===========================================================================================================================
//	CarPlay2iAP2InterfaceSetDelegate
//===========================================================================================================================
#if  1
void CarPlay2iAP2InterfaceSetDelegate( const CarPlay2iAP2InterfaceDelegate *inDelegate )
{
    gC2iDelegate = *inDelegate;
}
#endif
#if 1 
void iAP2InterfaceInitial()
{
    initialize();
#if 1
    iAP2InterfaceDelegateInit( &delegate );
    delegate.setup_f                    = _setup;
    delegate.sendNmeaSentence_f         = _sendNmeaSentence;
    delegate.sendPascdSentence_f        = _sendPascdSentence;
    delegate.sendRfcommData_f           = _sendRfcommData;
    delegate.sendWiFiInformation_f      = _sendWiFiInformation;
    delegate.sendCarPlayStartSession_f  = _sendCarPlayStartSession;
    delegate.sendOobBtPairingCompletionInformation_f        = _sendOobBtPairingCompletionInformation;
    delegate.sendResetMusicList_f       = _ResetMusicList;
    delegate.sendSelectMusicList_f      = _SelectMusicList;
    delegate.sendBackMusicList_f        = _BackMusicList;
    delegate.sendGetMusicList_f         = _GetMusicList;
    delegate.sendGetMusicPlayList_f     = _GetMusicPlayingList;
    delegate.sendSelectMusicPlayList_f  = _SelectMusicPlayingList;
    delegate.setMacAddr_f               = _SetMacAddr;
    iAP2InterfaceSetDelegate( &delegate );
#endif
}
#endif
